<script setup lang="ts">
import {Terminal} from "@xterm/xterm";
import {FitAddon} from "@xterm/addon-fit";
import {CanvasAddon} from '@xterm/addon-canvas';
import {WebLinksAddon} from '@xterm/addon-web-links';
import {Unicode11Addon} from '@xterm/addon-unicode11';
import {LigaturesAddon} from '@xterm/addon-ligatures'
import {computed, nextTick, onActivated, onMounted, onUnmounted, ref, unref, watch} from "vue";
import {ftpStorage, themeConfig} from "@/storage";
import {type ClientType, NewClient, StreamType} from "@/utils/client";
import {useDebounceFn, useEventListener} from '@vueuse/core'
import {findTree, isMac} from "@/utils";

interface Props {
  ftp_id: string
  loading?: boolean
}

const props = defineProps<Props>()

const theme = computed(() => themeConfig.value.isTerminalDark ? themeConfig.value.termThemeDark : themeConfig.value.termTheme)

const connection = ref(false)
const termRef = ref()
// ftp
const ftp = ref<DeployFtpType>({
  title: '成都147',
  id: 'test_1111',
  host: "192.168.0.147",
  port: 22,
  username: "root",
  password: "hrz81788178.com",
  system: 'linux'
})
// 终端 term
const term = new Terminal({
  fontSize: themeConfig.value.termFontSize || 12,
  // lineHeight: 1,
  fontFamily: "Consolas, Monaco, Menlo, 'Courier New', monospace",
  theme: theme.value,
  // 光标闪烁
  cursorBlink: true,
  cursorStyle: 'underline',
  scrollback: 1000,
  // tabStopWidth: 4,
  allowProposedApi: true,
  rightClickSelectsWord: false,
  allowTransparency: true,
  wordSeparator: './\\()"\'-:,.;<>~!@#$%^&*|+=[]{}`~ ?',
  screenReaderMode: false
});
// 终端自适应
const fitAddon = new FitAddon()
// // 支持更多的编码
const unicode11Addon = new Unicode11Addon()
// 使用canvas渲染
const canvasAddon = new CanvasAddon()
// 在可以访问 Node.js API（如 Electron）的环境中运行时，向 xterm.js 添加对编程连字的支持。
const ligaturesAddon = new LigaturesAddon()
// 让链接可以点击打开
const webLinksAddon = new WebLinksAddon(
  (event, uri) => {
    if (isMac ? event.metaKey : event.ctrlKey) window.open(uri);
  })
// ssh2.Client
const client: ClientType = new NewClient()
// shell
const stream = ref<StreamType | null>(null);
// 初始重连间隔为5秒
let reconnectTimeout = 5000;
// 重连尝试次数
let reconnectAttempts = 10;

/**
 * xterm事件
 */
const onTermEvent = () => {
  // 获取选中
  let copyText = ''
  term.onSelectionChange(() => {
    if (term.hasSelection()) {
      copyText = term.getSelection() || ''
    }
  })

  // 监听输入事件
  term.onData((key) => {
    if (props.loading) return
    if (['\x03', '\x16'].includes(key)) return;
    stream.value?.write(key)
  })

  // 按键事件
  term.onKey((event) => {
    if (props.loading) return
    if (event.key == '\x03') {
      if (copyText) {
        navigator.clipboard.writeText(copyText)
        copyText = ''
      } else {
        stream.value?.write(event.key)
      }
    } else if (event.key == '\x16') {
      navigator.clipboard.readText().then(clipText => {
        if (clipText) stream.value?.write(clipText)
      })
    }
  })
}

/**
 * 初始化shell
 */
const initShell = () => {
  console.log(`%c｡◕‿◕｡ SSH 连接成功。`, 'color: #3eaa88;font-size: 12px')
  client.shell({
    cols: term.cols,
    rows: term.rows,
    term: 'xterm'
  }, (error, channel) => {
    if (error) throw error;

    // 赋值
    stream.value = channel

    // 声明周期
    stream.value?.on('close', () => {
      // console.log('Stream :: close');
      client.end();
    }).on('data', (data: string) => {
      term.write(data)
    }).stderr.on('data', (err: string) => {
      term.write(err)
    })

    nextTick(() => {
      reconnectAttempts = 10
      // 连接成功
      connection.value = true
    })
  })
}

/**
 * 重连机制
 */
const onReconnectConnect = () => {
  if (reconnectAttempts < 1) return
  console.log(`%c｡◕﹏◕｡ SSH 已断开，${reconnectTimeout}秒后重试。`, 'color: #e6a23c;font-size: 12px')
  setTimeout(() => {
    client.connect(unref(ftp))
    reconnectAttempts--
  }, reconnectTimeout)
}

/**
 * 重置窗口
 */
const onTermResize = useDebounceFn(async () => {
  fitAddon.fit()
  if (termRef.value) {
    await new Promise(resolve => setTimeout(resolve, 100))
    stream.value?.setWindow(term.rows, term.cols, termRef.value.clientHeight, termRef.value.clientWidth)
  }
}, 100)

// 监听窗口比变化
const cleanup = useEventListener(window, 'resize', onTermResize)

// 销毁
const dispose = () => {
  cleanup()
  unicode11Addon.dispose()
  canvasAddon.dispose()
  webLinksAddon.dispose()
  ligaturesAddon.dispose()
  fitAddon.dispose()
  stream.value?.destroy()
  client.destroy()
  console.log(`%c｡◕﹏◕｡ SSH 已销毁。`, 'color: #f56c6c;font-size: 12px')
}

/**
 * 连接
 */
const initClient = () => {
  const ftpItem = findTree(unref(ftpStorage), item => item.id === props.ftp_id)
  if (ftpItem) {
    ftp.value = ftpItem
  }
  client.on('ready', initShell)
  client.on('close', () => {
    console.log('client：close')
  })
  client.on('error', () => {
    connection.value = false
    onReconnectConnect()
  })
  client.connect(unref(ftp))
}

watch(() => themeConfig.value.isTerminalDark, () => {
  term.options.theme = JSON.parse(JSON.stringify(unref(theme)))
})

onMounted(() => {
  term.open(termRef.value)
  term.loadAddon(unicode11Addon)
  term.loadAddon(webLinksAddon)
  term.loadAddon(ligaturesAddon)
  term.loadAddon(canvasAddon)
  term.loadAddon(fitAddon)
  onTermEvent()
  onTermResize()
  initClient()
  term.focus()
})

onUnmounted(dispose)

onActivated(() => {
  nextTick(() => {
    term.focus()
  })
})

defineExpose({term, client, stream, connection, onTermResize})
</script>

<template>
  <div ref="termRef" class="w-terminal"></div>
</template>
